package SVN::TeamTools::Plugins::Review;

use warnings;
use strict;
use Carp;
use Error qw(:try);

use SVN::TeamTools::Store::Config;

use DBI;
use SVN::Access;
use HTML::Template;
use DateTime;
use Data::Dumper;

my $conf;
my $logger;
BEGIN { $conf = SVN::TeamTools::Store::Config->new(); $logger = $conf->{logger}; }

sub hasAction {
	shift;
	my %args	= @_;
	my $action	= $args{action};
	return ("|store.review.comment|store.review.status|dsp.doc|dsp.config|dsp.review|get.review.groups|get.review.to-review|get.review.my-review|get.review.doc|proc.hook.post-commit|" =~ /\|\Q$action\E\|/);
}

sub getTemplate {
	shift;
	my %args	= @_;
	my $action	= $args{action};
	my $cgi		= $args{cgi};
	if ($action =~ /dsp.config/) {
		return HTML::Template->new( filename => 'SVN/TeamTools/Plugins/tmpl/review-config.tmpl', path => @INC );
	}
	if ($action =~ /dsp.review/) {
		return HTML::Template->new( filename => 'SVN/TeamTools/Plugins/tmpl/review-display.tmpl', path => @INC );
	}
	if ($action =~ /dsp.doc/) {
		return HTML::Template->new( filename => 'SVN/TeamTools/Plugins/tmpl/review-doc.tmpl', path => @INC );
	}
}

sub procHook {
	shift;
	my %args	= @_;
	my $action      = $args{action};
	my $param       = $args{param};

	$logger->info ("Test for review hook invoked, action $action");
	my $dbh = DBI->connect("dbi:SQLite:dbname=" . $conf->{reviewdb},"","") or die "Could not open database";
	my $sth1 = $dbh->prepare("UPDATE object SET obsolete=1 WHERE path=? AND obsolete=0");
	my $sth2 = $dbh->prepare("INSERT INTO object (path,rev,revdate,committer,approver_group,status, obsolete) VALUES (?,?,date(?),?,?,0,0)");
	# Commit rules
	if ($action =~ /proc.hook.post-commit/ and defined $conf->{config}->{reviewrules}->{rules}) {
		$logger->info ("Post commit review hook invoked");
		my $repopath    = $param->[0];
		my $rev	 = $param->[1];

		my $repo = SVN::TeamTools::Store::Repo->new();
		my $look = $repo->getLook(rev=>$rev);
		$look->date()   =~ /^(\d+-\d+-\d+\s\d+:\d+:\d+)\s/;
		my $date        = $1;

		my @files = ($look->added(),$look->updated());

		for my $rule (@{$conf->{config}->{reviewrules}->{rules}}) {
			$logger->debug ("... checking for $rule->{regex}->[0]");
			$logger->debug ("... in files ".Dumper(@files));
			my @results = grep ( m#$rule->{regex}->[0]# , @files );
			$logger->debug ("... resulting in :". Dumper(@results));
			for my $r (@results) {
				$logger->debug ("... processing $r");
				$sth1->bind_param (1, $r);
				$sth1->execute() or die $dbh->errstr;

				$sth2->bind_param (1, $r);
				$sth2->bind_param (2, $rev);
				$sth2->bind_param (3, $date);
				$sth2->bind_param (4, $look->author());
				$sth2->bind_param (5, $rule->{group});
				$sth2->execute() or die $dbh->errstr;

				$dbh->commit()     or die $dbh->errstr;

			}
		}
	}
	$dbh->disconnect();
}

sub getData {
	shift;
	my %args	= @_;
	my $action	= $args{action};
	my $param	= $args{param};

	my %result;
	my $authzpath = $conf->{config}->{svn}->{authz};
	if ( $authzpath !~ /^\// ) {
		$authzpath = $conf->{repo} . "/" . $authzpath;
	}
	my $acl = SVN::Access->new( acl_file => $authzpath );

	if ($action =~ /get.review.groups/) {
		my @groups;
		foreach my $group ( $acl->groups() ) {
			push(@groups, $group->name());
		}

		%result = ( groups => \@groups,);
		return \%result;
	} elsif ($action =~ /get.review.to-review/) {
		my $uname   = $ENV{REMOTE_USER};
		my @groups;
		foreach my $group ( $acl->groups() ) {
			if ( $group->member_exists($uname) ) {
				push( @groups, "'" . $group->name() . "'");
			}
		}
		my @result;
		my $dbh = DBI->connect("dbi:SQLite:dbname=" . $conf->{reviewdb},"","") or die "Could not open database";
		my $stmnt = "SELECT o.rev,o.revdate,o.path,o.committer,s.description FROM object o, status s WHERE o.obsolete=0 AND s.id=o.status AND approver_group IN (".join(',',@groups).") ORDER BY o.revdate DESC LIMIT 1000";
		$logger->debug ($stmnt);
		my $sth = $dbh->prepare($stmnt);
		$sth->execute() or die "Failing to execute statement";
		while (my ($rev, $revdate, $path, $committer, $status) = $sth->fetchrow()) {
			my $obj = {
				rev		=> $rev,
				revdate		=> $revdate,
				path		=> $path,
				committer	=> $committer,
				status		=> $status,
			};
			push (@result, $obj);
		}
		$dbh->disconnect();
		return \@result;
	} elsif ($action =~ /get.review.my-review/) {
		my $uname   = $ENV{REMOTE_USER};
		my @result;
		my $dbh = DBI->connect("dbi:SQLite:dbname=" . $conf->{reviewdb},"","") or die "Could not open database";
		my $stmnt = "SELECT o.rev,o.revdate,o.path,o.approver_group,s.description FROM object o, status s WHERE o.obsolete=0 AND s.id=o.status AND committer='$uname' ORDER BY o.revdate LIMIT 1000";
		$logger->debug ($stmnt);
		my $sth = $dbh->prepare($stmnt);
		$sth->execute() or die "Failing to execute statement";
		while (my ($rev, $revdate, $path, $approver_group, $status) = $sth->fetchrow()) {
			my $obj = {
				rev		=> $rev,
				revdate		=> $revdate,
				path		=> $path,
				approver_group	=> $approver_group,
				status		=> $status,
			};
			push (@result, $obj);
		}
		$dbh->disconnect();
		return \@result;
	} elsif ($action =~ /get.review.doc/) {
		my $uname   = $ENV{REMOTE_USER};
		my $result = {};

		my @groups;
		foreach my $group ( $acl->groups() ) {
			if ( $group->member_exists($uname) ) {
				push( @groups, "'" . $group->name() . "'");
			}
		}
		my $dbh = DBI->connect("dbi:SQLite:dbname=" . $conf->{reviewdb},"","") or die "Could not open database";
		my $stmnt = "SELECT o.id,o.committer,o.approver_group,o.approver,o.status,s.description FROM object o, status s WHERE o.obsolete=0 AND s.id=o.status AND o.rev=".$param->{rev}." AND o.path='".$param->{path}."' AND ( o.approver_group IN (".join(',',@groups).") OR o.committer='".$uname."' )";
		$logger->info ($stmnt);
		my $sth = $dbh->prepare($stmnt);
		$sth->execute() or die "Failing to execute statement";
		if ($result = $sth->fetchrow_hashref()) {
			$stmnt = "SELECT c.id, c.commenter,c.date,c.type,c.diffrev,c.diffpath,c.line,c.pos,c.comment FROM comment c WHERE c.object=".$result->{id}." ORDER BY c.id";
			$logger->debug ($stmnt);
			$sth = $dbh->prepare($stmnt);
			$sth->execute() or die "Failing to execute statement";
			my @res;
			while (my $sel = $sth->fetchrow_hashref()) {
				push(@res,$sel);
			}
			$result->{comments}=\@res;

## condition reversed for dev purposes, have to change later on 
			$result->{role}=(($result->{committer} eq $uname) ? "dev" : "app");
		} else {
			$result = {};
		}
		$dbh->disconnect();

		return $result;
	} elsif ($action =~ /store.review.status/) {
		my $uname   = $ENV{REMOTE_USER};
		my %result = (
			result	=> 0
		);

		my @groups;
		foreach my $group ( $acl->groups() ) {
			if ( $group->member_exists($uname) ) {
				push( @groups, $group->name());
			}
		}
		my $dbh = DBI->connect("dbi:SQLite:dbname=" . $conf->{reviewdb},"","") or die "Could not open database";
		my $stmnt = "SELECT status,obsolete,approver_group FROM object WHERE id=$param->{id}";
		$logger->info($stmnt);
		my $sth = $dbh->prepare($stmnt);
		$sth->execute() or die "Failing to execute statement";
		my $res = $sth->fetchrow_hashref() or die "Approval record does not exists anymore, database inconsistency";
		if ( $res->{status} > 0 ) {
			$result{message}="This one already seems to have been approved (or rejected)";
		} elsif ( $res->{obsolete} > 0 ) {
			$result{message}="Cannot approve/reject this one since a newer revision has been committed";
		} elsif ( ! scalar(grep ( /^\Q$res->{approver_group}\E$/ , @groups ))) {
			$result{message}="You don't have permission for this operation $res->{approver_group}<br><pre>".Dumper(@groups)."</pre>";
		} else {
			$result{result} = 1;
			$stmnt = "UPDATE object SET status = $param->{status}, approver='$uname' WHERE id=$param->{id}";
			$logger->info($stmnt);
			$sth = $dbh->prepare($stmnt);
			$sth->execute() or die "Failing to execute statement";
		}
		return \%result;
	} elsif ($action =~ /store.review.comment/) {
		my $uname   = $ENV{REMOTE_USER};
		my $dt = DateTime->now;
		my %result = (
			commenter	=> $uname,
			date		=> $dt->year()."-".$dt->month()."-".$dt->day(),
		);
		my $dbh = DBI->connect("dbi:SQLite:dbname=" . $conf->{reviewdb},"","") or die "Could not open database";
		my $stmnt = "INSERT INTO comment (object,commenter,date,type,diffrev,diffpath,line,pos,comment) VALUES(:object,:commenter,date('NOW'),:type,:diffrev,:diffpath,:line,:pos,:comment)";
		$logger->info($stmnt);
		my $sth = $dbh->prepare($stmnt);
		$sth->bind_param(":commenter",$uname);
		delete $param->{action};
		while (my ($key,$val) = each (%$param)) {
			$sth->bind_param (":$key",$val);
			$result{$key}=$val;
		}
		$sth->execute() or die "Cannot execute comment insert";
		$result{id}=$dbh->last_insert_id("","","comment","id");

		return \%result;
	}
}

sub createDB {
	my @ddl = (	"DROP TABLE IF EXISTS status",
			"DROP TABLE IF EXISTS object",
			"DROP TABLE IF EXISTS commenttype",
			"DROP TABLE IF EXISTS comment",
			"CREATE TABLE status (id INTEGER PRIMARY KEY,description TEXT)",
			"INSERT INTO status VALUES (0,'To be reviewed')",
			"INSERT INTO status VALUES (1,'Approved')",
			"INSERT INTO status VALUES (2,'Rejected')",
			"INSERT INTO status VALUES (8,'Deleted')",
			"INSERT INTO status VALUES (9,'Obsolete')",
			"CREATE TABLE object (id INTEGER PRIMARY KEY,path TEXT,rev INTEGER, revdate DATE,committer TEXT, approver_group TEXT,approver TEXT,status INTEGER, obsolete INTEGER, FOREIGN KEY (status) REFERENCES status(id))",
			"CREATE INDEX object_idx1 ON object (obsolete, approver_group)",
			"CREATE INDEX object_idx2 ON object (obsolete, committer)",
			"CREATE INDEX object_idx3 ON object (obsolete, path)",
			"CREATE INDEX object_idx4 ON object (obsolete, revdate)",
			"CREATE TABLE commenttype (id INTEGER PRIMARY KEY, desc TEXT)",
			"INSERT INTO commenttype VALUES (0,'general')",
			"INSERT INTO commenttype VALUES (1,'source')",
			"INSERT INTO commenttype VALUES (2,'diff')",
			"CREATE TABLE comment (id INTEGER PRIMARY KEY, object INTEGER, commenter TEXT, date DATE, type INTEGER, diffrev INTEGER, diffpath TEXT, line INTEGER, pos INTEGER, comment TEXT, FOREIGN KEY (object) REFERENCES object(id), FOREIGN KEY (type) REFERENCES commenttype(id))",
			"CREATE INDEX comment_idx1 ON comment (object, type, id)"
	);

	try {
		my $dbh = DBI->connect("dbi:SQLite:dbname=" . $conf->{reviewdb},"","") or die "Could not open database";

		for my $stmnt (@ddl) {
			$logger->debug ("Executing $stmnt");
			$dbh->do ($stmnt);
		} 
	} otherwise {
		my $exc = shift;
		croak "Error creating tables structure,  error: $exc";
	};
}
1;
